package com.miku.project.service.impl;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.request.WxPayOrderQueryV3Request;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.miku.mikuapicommon.model.entity.ProductInfo;
import com.miku.mikuapicommon.model.entity.ProductOrder;
import com.miku.mikuapicommon.model.entity.RechargeActivity;
import com.miku.mikuapicommon.model.entity.User;
import com.miku.project.common.ErrorCode;
import com.miku.project.config.EmailConfig;
import com.miku.project.exception.BusinessException;
import com.miku.project.mapper.ProductOrderMapper;
import com.miku.project.model.enums.PaymentStatusEnum;
import com.miku.project.model.vo.PaymentInfoVo;
import com.miku.project.model.vo.ProductOrderVo;
import com.miku.project.model.vo.UserVO;
import com.miku.project.service.*;
import com.miku.project.utils.EmailUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import static com.miku.project.constant.PayConstant.ORDER_PREFIX;
import static com.miku.project.constant.PayConstant.QUERY_ORDER_STATUS;
import static com.miku.project.model.enums.PayTypeStatusEnum.ALIPAY;
import static com.miku.project.model.enums.PaymentStatusEnum.CLOSED;
import static com.miku.project.model.enums.PaymentStatusEnum.NOTPAY;
import static com.miku.project.utils.WxPayUtil.getRequestHeader;

/**
 * 微信订单服务类
 */
@Service
@Slf4j
@Qualifier("WX")
@Primary
public class WxOrderServiceImpl extends ServiceImpl<ProductOrderMapper, ProductOrder> implements ProductOrderService {


    @Resource
    private RedisTemplate<String,Boolean> redisTemplate;

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private WxPayService wxPayService;
    @Resource
    private ProductInfoService productInfoService;
    @Resource
    private UserService userService;
    @Resource
    private PaymentInfoService paymentInfoService;
    @Resource
    private RechargeActivityService rechargeActivityService;
    @Resource
    private JavaMailSender mailSender;
    @Resource
    private EmailConfig emailConfig;
    /**
     * @param productId
     * @param loginUser
     * @param payType
     * @return
     */
    @Override
    public ProductOrderVo getProductOrder(Long productId, UserVO loginUser, String payType) {
        log.info("创建微信订单");
        //查询订单是否被创建
        ProductOrder productOrder = this.getOne(Wrappers.<ProductOrder>lambdaQuery()
                .eq(ProductOrder::getProductId, productId)
                .eq(ProductOrder::getStatus, NOTPAY.getValue())
                .eq(ProductOrder::getPayType, payType)
                .eq(ProductOrder::getUserId, loginUser.getId())
                .gt(ProductOrder::getExpirationTime, DateUtil.date(System.currentTimeMillis())));
        if (productOrder == null){
            return null;
        }
        //创建订单
        ProductOrderVo productOrderVo=new ProductOrderVo();
        BeanUtils.copyProperties(productOrder, productOrderVo);
        productOrderVo.setProductInfo(JSONUtil.toBean(productOrder.getProductInfo(), ProductInfo.class));
        productOrderVo.setTotal(productOrder.getTotal().toString());
        return productOrderVo;
    }

    /**
     * 保存产品订单
     *
     * @param productId
     * @param loginUser
     * @return
     */
    @Override
    public ProductOrderVo saveProductOrder(Long productId, UserVO loginUser) {
        ProductInfo productInfo=productInfoService.getById(productId);
        if (productInfo == null){
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR,"接口不存在");
        }
        //5分钟有效期
        Date date = DateUtil.date(System.currentTimeMillis());
        Date expirationTime = DateUtil.offset(date, DateField.MINUTE, 5);
        String orderNo=ORDER_PREFIX+ RandomUtil.randomNumbers(20);
        ProductOrder productOrder=new ProductOrder();
        productOrder.setUserId(loginUser.getId());
        productOrder.setProductId(productId);
        productOrder.setOrderNo(orderNo);
        productOrder.setOrderName(productInfo.getName());
        productOrder.setTotal(productInfo.getTotal());
        productOrder.setStatus(NOTPAY.getValue());
        productOrder.setPayType(ALIPAY.getValue());
        productOrder.setExpirationTime(expirationTime);
        productOrder.setProductInfo(JSONUtil.toJsonPrettyStr(productInfo));
        productOrder.setAddPoints(productInfo.getAddPoints());

        boolean saveresult=this.save(productOrder);
        if (!saveresult){
            throw new BusinessException(ErrorCode.OPERATION_ERROR,"保存订单失败");
        }
       //构建支付请求体
        WxPayUnifiedOrderV3Request wxPayRequest = new WxPayUnifiedOrderV3Request();
        WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
        amount.setTotal(productOrder.getTotal().intValue());
        wxPayRequest.setAmount(amount);
        wxPayRequest.setDescription(productOrder.getOrderName());
        //设置订单的过期时间为5分钟
        String format = DateUtil.format(expirationTime, "yyyy-MM-dd'T'HH:mm:ssXXX");
        wxPayRequest.setTimeExpire(format);
        wxPayRequest.setOutTradeNo(orderNo);
        try {
            String codeUrl = wxPayService.createOrderV3(TradeTypeEnum.NATIVE, wxPayRequest);
            if (StringUtils.isBlank(codeUrl)){
                throw new BusinessException(ErrorCode.OPERATION_ERROR,"获取支付二维码失败");
            }
            productOrder.setCodeUrl(codeUrl);
            //更新微信订单的二维码,不用重复创建
            boolean updateResult = this.updateProductOrder(productOrder);
            if (!updateResult){
                throw new BusinessException(ErrorCode.OPERATION_ERROR,"更新订单二维码失败");
            }
        } catch (WxPayException e) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR,e.getMessage());
        }
        //构建vo
        //构建vo
        ProductOrderVo productOrderVo=new ProductOrderVo();
        BeanUtils.copyProperties(productOrder,productOrderVo);
        productOrderVo.setAddPoints(productInfo.getAddPoints().intValue());
        productOrderVo.setCreateTime(productInfo.getCreateTime());
        productOrderVo.setDescription(productInfo.getDescription());
        productOrderVo.setProductType(productInfo.getProductType());
        productOrderVo.setProductInfo(productInfo);
        productOrderVo.setTotal(productInfo.getTotal().toString());
        return productOrderVo;
    }

    /**
     * 更新产品订单
     *
     * @param productOrder
     * @return
     */
    @Override
    public boolean updateProductOrder(ProductOrder productOrder) {
        String codeUrl = productOrder.getCodeUrl();
        Long id = productOrder.getId();
        ProductOrder order = new ProductOrder();
        order.setId(id);
        order.setCodeUrl(codeUrl);
        return this.updateById(order);
    }

    /**
     * 通过订单号获取产品订单
     *
     * @param orderNo
     * @return
     */
    @Override
    public ProductOrder getProductOrderByOutTradeNo(String orderNo) {
        ProductOrder productOrder = this.getOne(Wrappers.<ProductOrder>lambdaQuery().eq(ProductOrder::getOrderNo, orderNo));
        if (productOrder == null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"订单不存在");
        }
        return productOrder;
    }

    /**
     * 根据订单号修改订单状态
     *
     * @param orderNo
     * @param orderStatus
     * @return
     */
    @Override
    public boolean updateOrderStatusByOrderNo(String orderNo, String orderStatus) {
        boolean update = this.update(Wrappers.<ProductOrder>lambdaUpdate()
                .eq(ProductOrder::getOrderNo, orderNo)
                .set(ProductOrder::getStatus, orderStatus));
        return update;
    }

    /**
     * 付款通知
     * 处理付款通知
     *
     * @param notifyDate
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String doPaymentNotify(String notifyDate, HttpServletRequest request) {
        RLock lock = redissonClient.getLock("notify:WxOrder:lock");


        log.info("【微信支付回调】开始处理回调:{}",notifyDate);
        try {
            WxPayOrderNotifyV3Result result = wxPayService.parseOrderNotifyV3Result(notifyDate, getRequestHeader(request));
            //解密后的数据
            WxPayOrderNotifyV3Result.DecryptNotifyResult notifyResult=result.getResult();
            if (WxPayConstants.WxpayTradeStatus.SUCCESS.equals(notifyResult.getTradeState())){
                //获取订单号
                String outTradeNo = notifyResult.getOutTradeNo();
                if (lock.tryLock(0,-1, TimeUnit.MILLISECONDS)){
                    ProductOrder productOrder = this.getProductOrderByOutTradeNo(outTradeNo);
                    //处理重复通知
                    if (PaymentStatusEnum.SUCCESS.getValue().equals(productOrder.getStatus())){
                      redisTemplate.delete(QUERY_ORDER_STATUS+outTradeNo);
                      return WxPayNotifyResponse.success("支付成功");
                    }
                    //更新订单状态
                    boolean updateOrderStatusByOrderNo = this.updateOrderStatusByOrderNo(outTradeNo, PaymentStatusEnum.SUCCESS.getValue());
                    //更新用户积分
                    boolean addWalletBalance = userService.addWalletBalance(productOrder.getUserId(), productOrder.getTotal().intValue());
                    //保存支付记录
                    PaymentInfoVo paymentInfoVo = new PaymentInfoVo();
                    BeanUtils.copyProperties(productOrder,paymentInfoVo);
                    WxPayOrderQueryV3Result.Payer payer = new WxPayOrderQueryV3Result.Payer();
                    payer.setOpenid(notifyResult.getPayer().getOpenid());
                    WxPayOrderQueryV3Result.Amount amount = new WxPayOrderQueryV3Result.Amount();
                    amount.setCurrency(notifyResult.getAmount().getCurrency());
                    amount.setPayerTotal(notifyResult.getAmount().getPayerTotal());
                    amount.setPayerCurrency(notifyResult.getAmount().getPayerCurrency());
                    amount.setTotal(notifyResult.getAmount().getTotal());
                    paymentInfoVo.setAmount(amount);
                    paymentInfoVo.setPayer(payer);
                    boolean paymentInfo = paymentInfoService.createPaymentInfo(paymentInfoVo);
                    //更新活动表
                    boolean saveRechargeActivity = saveRechargeActivity(productOrder);
                    if (updateOrderStatusByOrderNo && addWalletBalance && paymentInfo && saveRechargeActivity){
                        log.info("【微信支付回调】处理回调成功");
                        //发送邮件
                        redisTemplate.delete(QUERY_ORDER_STATUS+outTradeNo);
                        sendSuccessEmail(productOrder,String.valueOf(productOrder.getTotal()));
                        return WxPayNotifyResponse.success("支付成功");
                    }
                }
            }
            if (WxPayConstants.WxpayTradeStatus.PAY_ERROR.equals(notifyResult.getTradeState())){
                log.error("【微信支付回调】支付失败:{}",result);
                throw new BusinessException(ErrorCode.OPERATION_ERROR,"支付失败");
            }
            if (WxPayConstants.WxpayTradeStatus.USER_PAYING.equals(notifyResult.getTradeState())){
                log.error("【微信支付回调】用户支付中:{}",result);
                throw new BusinessException(ErrorCode.OPERATION_ERROR,"用户支付中");
            }
            throw new BusinessException(ErrorCode.OPERATION_ERROR,"支付失败");
        } catch (Exception e) {
            log.error("【支付失败】"+e.getMessage());
            try {
                throw new WxPayException("支付失败");
            }catch (WxPayException ex) {
                throw new BusinessException(ErrorCode.OPERATION_ERROR, "支付失败");
            }
        }finally {
            if (lock.isHeldByCurrentThread()){
                System.out.println("释放锁:"+Thread.currentThread().getId());
                lock.unlock();
            }
        }
    }

    /**
     * 检查订单状态(微信查单接口)
     * 删除超时订单
     *
     * @param productOrder
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processingTimedOutOrders(ProductOrder productOrder) {
        String orderNo = productOrder.getOrderNo();
        WxPayOrderQueryV3Request request = new WxPayOrderQueryV3Request();
        request.setOutTradeNo(orderNo);
        WxPayOrderQueryV3Result result;

        try {
            WxPayOrderQueryV3Result queryV3Result = wxPayService.queryOrderV3(request);
            String tradeState = queryV3Result.getTradeState();
            if (tradeState.equals(WxPayConstants.WxpayTradeStatus.SUCCESS)){
                //订单已支付
                this.updateOrderStatusByOrderNo(productOrder.getOrderNo(), WxPayConstants.WxpayTradeStatus.SUCCESS);
                //用户余额补发
                userService.addWalletBalance(productOrder.getUserId(), productOrder.getTotal().intValue());
                //创建支付记录
                PaymentInfoVo paymentInfoVo = new PaymentInfoVo();
                BeanUtils.copyProperties(productOrder,paymentInfoVo);
                paymentInfoService.createPaymentInfo(paymentInfoVo);
                //更新活动表
                saveRechargeActivity(productOrder);
                //发送邮件
                sendSuccessEmail(productOrder,String.valueOf(productOrder.getTotal()));
                log.info("超时订单:{},状态已更新",orderNo);
            }
            if (tradeState.equals(NOTPAY.getValue())||tradeState.equals(CLOSED.getValue())){
                closedOrderByOrderNo(productOrder.getOrderNo());
                this.updateOrderStatusByOrderNo(productOrder.getOrderNo(), WxPayConstants.WxpayTradeStatus.CLOSED);
                log.info("超时订单{},订单已关闭",orderNo);
            }
          redisTemplate.delete(QUERY_ORDER_STATUS+orderNo);
        } catch (WxPayException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 发送邮件
     * @param productOrder
     * @param totalAmount
     */
    private void sendSuccessEmail(ProductOrder productOrder, String totalAmount) {
        User user = userService.getById(productOrder.getUserId());
        if (StringUtils.isNotBlank(user.getEmail())){
            ProductOrder outTradeNo = this.getProductOrderByOutTradeNo(productOrder.getOrderNo());
            try {
                new EmailUtil().sendPaySuccessEmail(user.getEmail(),mailSender,emailConfig,outTradeNo.getOrderName(),String.valueOf(totalAmount));
                log.info("发送邮件:{}，成功",user.getEmail());
            } catch (Exception e) {
                log.error("发送邮件:{}，失败",user.getEmail());
            }


        }
    }

    /**
     * 保存充值活动
     * @param productOrder
     * @return
     */
    private boolean saveRechargeActivity(ProductOrder productOrder) {
        boolean update = rechargeActivityService.update(Wrappers.<RechargeActivity>lambdaUpdate()
                .set(RechargeActivity::getUserId, productOrder.getUserId())
                .set(RechargeActivity::getProductId, productOrder.getProductId())
                .set(RechargeActivity::getOrderNo, productOrder.getOrderNo()));
        if (!update){
            throw new BusinessException(ErrorCode.OPERATION_ERROR,"保存充值活动失败");
        }
        return true;
    }

    public void closedOrderByOrderNo(String outTradeNo)  {
        try {
            wxPayService.closeOrderV3(outTradeNo);
        } catch (WxPayException e) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR,"关闭订单失败");
        }
    }
}
